// Copyright (c) 2011, http://code.google.com/p/example-xplane-plugins/
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of "example-xplane-plugins" nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL "example-xplane-plugins" BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// NOTE: This file originally derives from Sandy Barbour's WidgetTest code.
//
// This code is just to test the aircraft control class - the GUI should be put
// into separate files etc.


#include "Multiplane.h"

#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMMenus.h"
#include "XPLMUtilities.h"
#include "XPWidgets.h"
#include "XPStandardWidgets.h"
#include "XPWidgetsEx.h"

#include "XPLMCamera.h"
#include "XPUIGraphics.h"
#include "XPWidgetUtils.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vector>
#include <string>

#include "XPUserAircraft.h"
#include "XPMultiAircraft.h"


static bool s_windowCreated;
static XPLMMenuID	s_menuID = 0;

static XPWidgetID s_windowMain = 0;

static XPWidgetID s_buttonSetPos = 0;
static XPWidgetID s_buttonAcquire = 0;

static XPWidgetID s_captionLatitudeDeg = 0;
static XPWidgetID s_captionLongitudeDeg = 0;
static XPWidgetID s_captionaltitudeM = 0;

static XPWidgetID s_textLatitudeDeg = 0;
static XPWidgetID s_textLongitudeDeg = 0;
static XPWidgetID s_textaltitudeM = 0;

static XPWidgetID s_captionHeadingDeg = 0;
static XPWidgetID s_captionPitchDeg = 0;
static XPWidgetID s_captionRollDeg = 0;

static XPWidgetID s_textHeadingDeg = 0;
static XPWidgetID s_textPitchDeg = 0;
static XPWidgetID s_textRollDeg = 0;


static XPUserAircraft  s_userModel;
static XPMultiAircraft s_multiplayerModel1;
static XPMultiAircraft s_multiplayerModel2;
static XPMultiAircraft s_multiplayerModel3;

static XPWidgetID s_popupFeed = 0;

static eMenuMode s_mode = MENU_MODE_JOYSTICK;
static eMenuMode s_oldmode = s_mode;
static bool s_gotAircraft = false;


static const int MAX_POPUP_TEXT_LENGTH = 4096;
static const int MAX_WIDGET_STRING_LENGTH = 256;

static char szPopupText[MAX_POPUP_TEXT_LENGTH] = {0};

static float MODEL_UPDATE_RATE = 50.0f;
static float MODEL_UPDATE_PERIOD = 1.0f/MODEL_UPDATE_RATE;

// Useful constants
static const double PI = 3.14159265358979;
static const double RAD_TO_DEG = 180.0 / PI;
static const double DEG_TO_RAD = 1.0 / RAD_TO_DEG;


// Model
static ModelState s_dialogModelState;

#define PLUGIN_NAME         "Multiplane"
#define PLUGIN_ID           "exam.examples.multiplane"
#define PLUGIN_DESCRIPTION  "Plug-in that controls own aircraft and the multiplayer aircraft."

#define MENU_ITEM_NAME      "Open"
static char s_menuItemData[] = MENU_ITEM_NAME;

/*------------------------------------------------------------------------*/

PLUGIN_API int XPluginStart(char *outName,
                            char *outSig,
                            char *outDesc)
{
  // Can't use strcpy_s - overwites the string memory with 0xFD debug bytes before string copying
  // I would need to know char array lengths in order to use it safely
  strcpy(outName, PLUGIN_NAME);
  strcpy(outSig,  PLUGIN_ID);
  strcpy(outDesc, PLUGIN_DESCRIPTION);

  // Build menu
  int item(XPLMAppendMenuItem(XPLMFindPluginsMenu(), PLUGIN_NAME, NULL, 1));

  s_menuID = XPLMCreateMenu("Multiplane", XPLMFindPluginsMenu(), item, WidgetsMenuHandler, NULL);

  XPLMAppendMenuItem(s_menuID, MENU_ITEM_NAME, reinterpret_cast<void*>(s_menuItemData), 1);

  // Used by widget to make sure only one widgets instance created
  s_windowCreated = false;


  // Preload Popup
  szPopupText[0] = '\0';
  strcat( szPopupText, "Joystick");
  strcat( szPopupText, ";" );
  strcat( szPopupText, "Textbox");
  strcat( szPopupText, ";" );
  strcat( szPopupText, "Model");
  strcat( szPopupText, ";" );

  // The function "FlightLoopCallBack" will be called to update the model [-1.0 implies every frame]
  XPLMRegisterFlightLoopCallback(FlightLoopCallBack, -1.0, NULL);

  // Return 1 to indicate we have handled this DLL call
  return 1;
}

/*------------------------------------------------------------------------*/

PLUGIN_API void	XPluginStop(void)
{
  // Remove the menu item from the toolbar
   XPLMDestroyMenu(s_menuID);

  // Stops XPlane from calling the "callback"
  XPLMUnregisterFlightLoopCallback(FlightLoopCallBack, NULL);

  // Clean up - other items if the menu had been selected in the past
  if (s_windowCreated)
  {
    XPDestroyWidget(s_windowMain, 1);
    s_windowCreated = false;
  }
}

/*------------------------------------------------------------------------*/

PLUGIN_API void XPluginDisable(void)
{
  XPAircraft::ReleaseAllAircraft();
}

/*------------------------------------------------------------------------*/

PLUGIN_API int XPluginEnable(void)
{
  return 1;
}

/*------------------------------------------------------------------------*/

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, long inMsg, void * inParam)
{
}

/*------------------------------------------------------------------------*/

// Handle any menu messages, only one, to created widget dialog
void WidgetsMenuHandler(void * mRef, void * iRef)
{
  // strcmp returns 0 when an exact match is found.
  if (0==strcmp(reinterpret_cast<char*>(iRef), MENU_ITEM_NAME))
  {
    // If the window has not been created
    if (!s_windowCreated)
    {
      // Create the window widgets
      CreateWidgets(300, 650, 200);
      s_windowCreated = true;
    }
    else  // The window has already been created and just needs to be made visible.
    {
      // If not visible - make it visible.
      if(!XPIsWidgetVisible(s_windowMain))
      {
        XPShowWidget(s_windowMain);
      }
    }
  }
}

/*------------------------------------------------------------------------*/

// This creates the widgets dialog and any controls
void CreateWidgets(int x, int y, int w, int h)
{
  // Widget Coordinate system is X across and Y down with the origin in the TOP-LEFT

  const int xIndent(10);
  const int yIndent(20);
  const int textBoxHeight(20);
  const int captionWidth(100);

  // Auto
  if (h<=0)
  {
    h = 2*yIndent + (1+1+7)*textBoxHeight; 
  }

  int top(y);
  int bottom(y - h);
  int left(x);
  int right(x + w);
  int divider(0);

  // MAIN WINDOW
  s_windowMain = XPCreateWidget(left, top, right, bottom,
    1,	// Visible
    "Aircraft Position",	// desc
    1,		// root
    NULL,	// no container
    xpWidgetClass_MainWindow);

  XPSetWidgetProperty(s_windowMain, xpProperty_MainWindowHasCloseBoxes, 1);

  // ADD CONTROLS INSIDE
  top     = top   - yIndent;
  bottom  = top   - textBoxHeight;
  left    = left  + xIndent;
  right   = right - xIndent;
  divider = left  + captionWidth;

  // BUTTON FOR ACQUIRE AIRCRAFT IN WINDOW
  top     = top;
  bottom  = top - textBoxHeight;

  s_buttonAcquire = XPCreateWidget(left, top, right, bottom,
    1,
    "Acquire Aircraft",
    0,
    s_windowMain,
    xpWidgetClass_Button);

  XPSetWidgetProperty(s_buttonAcquire, xpProperty_ButtonType, xpPushButton);
  XPSetWidgetProperty(s_buttonAcquire, xpProperty_ButtonBehavior, xpButtonBehaviorPushButton);

  // BUTTON IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_popupFeed = XPCreatePopup(left, top, right, bottom,
    1,
    szPopupText,
    s_windowMain);

  // BUTTON IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_buttonSetPos = XPCreateWidget(left, top, right, bottom,
    0,
    "Set Pos",
    0,
    s_windowMain,
    xpWidgetClass_Button);

  XPSetWidgetProperty(s_buttonSetPos, xpProperty_ButtonType, xpPushButton);
  XPSetWidgetProperty(s_buttonSetPos, xpProperty_ButtonBehavior, xpButtonBehaviorPushButton);

  // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionLatitudeDeg = XPCreateWidget(left, top, divider, bottom,
    1,
    "Latitude(deg)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textLatitudeDeg = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

  // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionLongitudeDeg = XPCreateWidget(left, top, divider, bottom,
    1,
    "Longitude(deg)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textLongitudeDeg = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

  // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionaltitudeM = XPCreateWidget(left, top, divider, bottom,
    1,
    "Altitude(m)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textaltitudeM = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

  // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionHeadingDeg = XPCreateWidget(left, top, divider, bottom,
    1,
    "Heading(deg)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textHeadingDeg = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

    // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionPitchDeg = XPCreateWidget(left, top, divider, bottom,
    1,
    "Pitch(deg)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textPitchDeg = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

  // TEXTBOX IN WINDOW
  top     = bottom;
  bottom  = top - textBoxHeight;

  s_captionRollDeg = XPCreateWidget(left, top, divider, bottom,
    1,
    "Roll(deg)",
    0,
    s_windowMain,
    xpWidgetClass_Caption);

  s_textRollDeg = XPCreateWidget(divider, top, right, bottom,
    1,
    "000.0000000",
    0,
    s_windowMain,
    xpWidgetClass_TextField);

  // HANDLER - The function "WidgetsHandler" gets called when XPlane wants to update the window and its contents
  XPAddWidgetCallback(s_windowMain, WidgetsHandler);

  // GET HANDLES TO DATAREFS - we do this once here - much faster than looking them up every tick.
  s_userModel.BindToXPlaneAircraft();
}

/*------------------------------------------------------------------------*/

// Handle any widget messages - will also be called every frame when dialog is visible
int	WidgetsHandler(XPWidgetMessage inMessage,
                       XPWidgetID inWidget,
                       long inParam1,
                       long inParam2)
{
  int retVal(0);

  // Handle specific message from XPlane
  switch (inMessage)
  {
  case xpMessage_CloseButtonPushed:
    {
      // Close button pressed, only hide the widget, rather than destroying it.
      if (s_windowCreated)
      {
        XPHideWidget(s_windowMain);
      }
      retVal = 1;
    }
    break;
  case xpMessage_PopupNewItemPicked:
    {
      // This handles the popup item picked message
      // i.e. when you selecte another popup entry.
      s_mode = static_cast<eMenuMode>(XPGetWidgetProperty(s_popupFeed, xpProperty_PopupCurrentItem, NULL));

      bool overrideAircraft(false);

      // GUI Modifications & XPlane Flight model override
      XPHideWidget(s_buttonSetPos);
      switch (s_mode)
      {
      case MENU_MODE_JOYSTICK:
        {
          overrideAircraft = false;
        }
        break;
      case MENU_MODE_TEXTBOX:
        {
          overrideAircraft = true;
          XPShowWidget(s_buttonSetPos);
        }
        break;
      case MENU_MODE_MODEL:
        {
          overrideAircraft = true;
        }
        break;
      default:
        {
          overrideAircraft = false;
        }
      } 
      // XPlane Flight model override
      s_userModel.SetOverrideXPlaneState(overrideAircraft);
      
      retVal = 1;
    }
    break;
  case xpMsg_PushButtonPressed:
    {
      // Test for a button pressed

      // This test set the popup index to the number
      // entered into the PopupInputTextEdit edit box
      if (static_cast<long long>(inParam1) == reinterpret_cast<long long>(s_buttonSetPos))
      {
        if (MENU_MODE_TEXTBOX == s_mode)
        {
          // Update pos from dialog
          ReadFromDialog(
            s_dialogModelState.latitudeDeg, s_dialogModelState.longitudeDeg, s_dialogModelState.altitudeM,
            s_dialogModelState.headingDeg, s_dialogModelState.pitchDeg, s_dialogModelState.rollDeg);

          // Set aircraft position
          s_userModel.SetModelState(s_dialogModelState);
        }

        // Handled
        retVal = 1;
      }
      else if(static_cast<long long>(inParam1) == reinterpret_cast<long long>(s_buttonAcquire))
      {
        if (!s_gotAircraft)
        {
          XPAircraft::AcquireAllAircraft();

          // Reset models
          ModelState initialModelState;
          initialModelState.latitudeDeg   = 49.6;
          initialModelState.longitudeDeg  = 10.55;
          initialModelState.altitudeM     = 2500.0;
          initialModelState.headingDeg    = 0.0f;
          initialModelState.pitchDeg      = 0.0f;
          initialModelState.rollDeg       = 0.0f;

          s_userModel.SetModelState(initialModelState);
          initialModelState.longitudeDeg += 0.05; // Move front
          s_multiplayerModel1.SetModelState(initialModelState);
          initialModelState.latitudeDeg -= 0.01; // Move side
          s_multiplayerModel2.SetModelState(initialModelState);
          initialModelState.latitudeDeg += 0.02; // Move other side
          s_multiplayerModel3.SetModelState(initialModelState);

          s_oldmode = s_mode;
          s_mode = MENU_MODE_MODEL;

          s_userModel.BindToXPlaneAircraft();
          s_multiplayerModel1.BindToXPlaneAircraft(1);
          s_multiplayerModel2.BindToXPlaneAircraft(2);
          s_multiplayerModel3.BindToXPlaneAircraft(3);
          s_userModel.SetOverrideXPlaneState(true);
          s_multiplayerModel1.SetOverrideXPlaneState(true);
          s_multiplayerModel2.SetOverrideXPlaneState(true);
          s_multiplayerModel3.SetOverrideXPlaneState(true);

          XPSetWidgetDescriptor(s_buttonAcquire, "Release Aircraft");
          s_gotAircraft = true;
        }
        else
        {
          s_userModel.SetOverrideXPlaneState(false);
          s_multiplayerModel1.SetOverrideXPlaneState(false);
          s_multiplayerModel2.SetOverrideXPlaneState(false);
          s_multiplayerModel3.SetOverrideXPlaneState(false);
          s_userModel.UnbindFromXPlaneAircraft();
          s_multiplayerModel1.UnbindFromXPlaneAircraft();
          s_multiplayerModel2.UnbindFromXPlaneAircraft();
          s_multiplayerModel3.UnbindFromXPlaneAircraft();
          XPAircraft::ReleaseAllAircraft();
          
          s_mode = s_oldmode;

          XPSetWidgetDescriptor(s_buttonAcquire, "Acquire Aircraft");
          s_gotAircraft = false;
        }

        // Handled
        retVal = 1;
      }
      else
      {
        // Do nothing
      }
    }
  default:
    {
      // Do nothing
    }
  }


  // Want the position dialog box to be updated in some modes
  switch (s_mode)
  {
  case MENU_MODE_JOYSTICK:
  case MENU_MODE_MODEL:
    {      
      // Update the values from (XPlane/Our) flight model
      WriteToDialog(
        s_dialogModelState.latitudeDeg, s_dialogModelState.longitudeDeg, s_dialogModelState.altitudeM,
        s_dialogModelState.headingDeg, s_dialogModelState.pitchDeg, s_dialogModelState.rollDeg);
    }
    break;
  case MENU_MODE_TEXTBOX:
  default:
    {
      // Do nothing - use edits values
    }
  }

  return retVal;
}


// MODEL LOOP - Other callbacks dont get called if Widget is hidden or closed!
float FlightLoopCallBack(float elapsedMe, float elapsedSim, int counter, void * refcon)
{
  // Want the model to be updated in some modes
  switch (s_mode)
  {
  case MENU_MODE_MODEL:
    {
      // Update model using its own calculations as the source
      s_userModel.UpdateModel(elapsedSim);
      // Dialog needs latest model state
      s_userModel.GetModelState(s_dialogModelState);
      // DRAW model
      s_userModel.UpdateModelToXPlane();
    }
    break;
  case MENU_MODE_JOYSTICK:
    {
      // Update model using XPlane as the source
      s_userModel.UpdateModelFromXPlane();
      // Dialog needs latest model state
      s_userModel.GetModelState(s_dialogModelState);
    }
    break;
  case MENU_MODE_TEXTBOX:
  default:
    {
      // Keep model informed of latest state from dialog
      s_userModel.SetModelState(s_dialogModelState);
      // DRAW model
      s_userModel.UpdateModelToXPlane();
    }
  }

  // Either way update the non-player models
  s_multiplayerModel1.UpdateModel(elapsedSim);
  s_multiplayerModel2.UpdateModel(elapsedSim);
  s_multiplayerModel3.UpdateModel(elapsedSim);
  // DRAW model
  s_multiplayerModel1.UpdateModelToXPlane();
  s_multiplayerModel2.UpdateModelToXPlane();
  s_multiplayerModel3.UpdateModelToXPlane();

  // Time at which to TRY and call the model again
  return MODEL_UPDATE_PERIOD;
}



// DIALOG
void WriteToDialog(const double latitudeDeg, const double longitudeDeg, const double altitudeM,
                   const float headingDeg, const float pitchDeg, const float rollDeg)
{
  // Set text
  char buffer[MAX_WIDGET_STRING_LENGTH] = {0};

  // Position
  sprintf(buffer,"%3.6f",latitudeDeg);
  XPSetWidgetDescriptor(s_textLatitudeDeg, buffer);
  sprintf(buffer,"%3.6f",longitudeDeg);
  XPSetWidgetDescriptor(s_textLongitudeDeg, buffer);
  sprintf(buffer,"%3.2f",altitudeM);
  XPSetWidgetDescriptor(s_textaltitudeM, buffer);

  // Orientations
  sprintf(buffer,"%3.1f",headingDeg);
  XPSetWidgetDescriptor(s_textHeadingDeg, buffer);
  sprintf(buffer,"%3.1f",pitchDeg);
  XPSetWidgetDescriptor(s_textPitchDeg, buffer);
  sprintf(buffer,"%3.1f",rollDeg);
  XPSetWidgetDescriptor(s_textRollDeg, buffer);
}


/*------------------------------------------------------------------------*/

void ReadFromDialog(double& latitudeDeg, double& longitudeDeg, double& altitudeM,
                    float& headingDeg, float& pitchDeg, float& rollDeg)
{
  // Get text
  char buffer[MAX_WIDGET_STRING_LENGTH] = {0};

  // Position
  XPGetWidgetDescriptor(s_textLatitudeDeg, buffer, sizeof(buffer));
  latitudeDeg = atof(buffer);
  XPGetWidgetDescriptor(s_textLongitudeDeg, buffer, sizeof(buffer));
  longitudeDeg = atof(buffer);
  XPGetWidgetDescriptor(s_textaltitudeM, buffer, sizeof(buffer));
  altitudeM = atof(buffer);

  // Orientations
  XPGetWidgetDescriptor(s_textHeadingDeg, buffer, sizeof(buffer));
  headingDeg = static_cast<float>(atof(buffer));
  XPGetWidgetDescriptor(s_textPitchDeg, buffer, sizeof(buffer));
  pitchDeg = static_cast<float>(atof(buffer));
  XPGetWidgetDescriptor(s_textRollDeg, buffer, sizeof(buffer));
  rollDeg = static_cast<float>(atof(buffer));
}

